﻿namespace MicroCloud.Identity
{
    /// <summary>
    /// 身份标识模块基类
    /// </summary>
    /// <typeparam name="TUserStore">用户存储</typeparam>
    /// <typeparam name="TRoleStore">角色存储</typeparam>
    /// <typeparam name="TOrganizationStore">组织机构存储</typeparam>
    /// <typeparam name="TUser">用户实体类型</typeparam>
    /// <typeparam name="TUserKey">用户编号类型</typeparam>
    /// <typeparam name="TUserClaim">用户声明实体类型</typeparam>
    /// <typeparam name="TUserClaimKey">用户声明编号类型</typeparam>
    /// <typeparam name="TRole">角色实体类型</typeparam>
    /// <typeparam name="TRoleKey">角色编号类型</typeparam>
    /// <typeparam name="TOrganization">组织机构实体类型</typeparam>
    /// <typeparam name="TOrganizationKey">组织机构编号类型</typeparam>
    [Description("身份标识模块")]
    [DependsOnPacks(typeof(EventBusPack))]
    public abstract class IdentityPackBase<TUserStore, TRoleStore, TOrganizationStore, TUser, TUserKey, TUserClaim, TUserClaimKey, TRole, TRoleKey, TOrganization, TOrganizationKey> : PackBase
        where TUserStore : class, IUserStore<TUser>, IUserOrganizationStore<TUser>
        where TRoleStore : class, IRoleStore<TRole>, IRoleUserStore<TRole>, IRoleOrganizationStore<TRole>
        where TOrganizationStore : class, IOrganizationStore<TOrganization>, IOrganizationClaimStore<TOrganization>, IQueryableOrganizationStore<TOrganization>
        where TUser : UserBase<TUserKey>
        where TUserKey : struct, IEquatable<TUserKey>
        where TUserClaim : UserClaimBase<TUserClaimKey, TUserKey>
        where TUserClaimKey : struct, IEquatable<TUserClaimKey>
        where TRole : RoleBase<TRoleKey, TUserKey>
        where TRoleKey : struct, IEquatable<TRoleKey>
        where TOrganization : OrganizationBase<TOrganizationKey, TUserKey>
        where TOrganizationKey : struct, IEquatable<TOrganizationKey>
    {
        #region 属性
        /// <summary>
        /// 获取 模块级别
        /// <para>级别越小越先启动</para>
        /// </summary>
        public override PackLevel Level => PackLevel.Framework;

        /// <summary>
        /// 获取 模块启动顺序编号
        /// <para>模块启动的顺序先按级别启动，级别内部再按此顺序启动，级别默认为0，表示无依赖，需要在同级别有依赖顺序的时候，再重写为 >0 的顺序值。</para>
        /// </summary>
        public override int OrderNo => 2;

        #endregion

        #region 方法
        /// <summary>
        /// 将模块服务添加到依赖注入服务容器中
        /// </summary>
        /// <param name="services">依赖注入服务容器</param>
        /// <returns></returns>
        public override IServiceCollection AddServices(IServiceCollection services)
        {
            services.AddScoped<IUserStore<TUser>, TUserStore>();
            services.AddScoped<IUserOrganizationStore<TUser>, TUserStore>();

            services.AddScoped<IRoleStore<TRole>, TRoleStore>();
            services.AddScoped<IRoleUserStore<TRole>, TRoleStore>();
            services.AddScoped<IRoleOrganizationStore<TRole>, TRoleStore>();

            services.AddScoped<IOrganizationStore<TOrganization>, TOrganizationStore>();
            services.AddScoped<IOrganizationClaimStore<TOrganization>, TOrganizationStore>();
            services.AddScoped<IQueryableOrganizationStore<TOrganization>, TOrganizationStore>();

            services.AddEventHandler<OnlineUserCacheRemoveEventHandler>();

            //在线用户缓存
            services.TryAddScoped<IOnlineUserProvider, OnlineUserProvider<TUser, TUserKey, TUserClaim, TUserClaimKey, TRole, TRoleKey, TOrganization, TOrganizationKey>>();
            //组织机构信息提供者
            services.TryAddScoped<IOrganizationProvider, OrganizationProvider<TUser, TUserKey, TRole, TRoleKey, TOrganization, TOrganizationKey>>();

            // 替换 IPrincipal ，设置用户主键类型，用以在Repository进行审计时注入正确用户主键类型
            services.Replace(new ServiceDescriptor(typeof(IPrincipal),
                provider =>
                {
                    IHttpContextAccessor accessor = provider.GetService<IHttpContextAccessor>();
                    ClaimsPrincipal principal = accessor?.HttpContext?.User;
                    if (principal != null && principal.Identity is ClaimsIdentity identity)
                    {
                        //设置用户ID类型
                        PropertyInfo property = typeof(TUser).GetProperty("Id");
                        if (property != null)
                        {
                            identity.RemoveClaims(SettingKeyConstants.UserIdTypeName);
                            identity.AddClaim(new Claim(SettingKeyConstants.UserIdTypeName, property.PropertyType.FullName));
                        }

                        //设置组织机构ID类型
                        property = typeof(TOrganization).GetProperty("Id");
                        if (property != null)
                        {
                            identity.RemoveClaims(SettingKeyConstants.OrgIdTypeName);
                            identity.AddClaim(new Claim(SettingKeyConstants.OrgIdTypeName, property.PropertyType.FullName));
                        }
                    }

                    return principal;
                },
                ServiceLifetime.Transient));

            Action<IdentityOptions> identityOptionsAction = IdentityOptionsAction();
            IdentityBuilder builder = services.AddIdentity<TUser, TRole>(identityOptionsAction);

            services.Replace(new ServiceDescriptor(typeof(IdentityErrorDescriber), typeof(IdentityErrorDescriberZhHans), ServiceLifetime.Scoped));

            OnIdentityBuild(builder);

            Action<CookieAuthenticationOptions> cookieOptionsAction = CookieOptionsAction();
            if (cookieOptionsAction != null)
            {
                services.ConfigureApplicationCookie(cookieOptionsAction);
            }

            return services;
        }

        /// <summary>
        /// 重写以实现<see cref="IdentityOptions"/>的配置
        /// </summary>
        /// <returns></returns>
        protected virtual Action<IdentityOptions> IdentityOptionsAction()
        {
            return options =>
            {
                #region "用于配置登录的选项"
                //是否需要已确认的账户才能登录，默认为false
                options.SignIn.RequireConfirmedAccount = false;
                //是否需要确认的电子邮件地址才能登录，默认为false
                options.SignIn.RequireConfirmedEmail = false;
                //是否需要确认的电话号码才能登录，默认为false
                options.SignIn.RequireConfirmedPhoneNumber = false;
                #endregion
                #region "指定密码要求的选项"
                //密码是否必须包含数字，默认为true
                options.Password.RequireDigit = false;
                //密码的最小长度，默认为6
                options.Password.RequiredLength = 0;
                //密码必须包含的最小唯一字符数，默认为1
                options.Password.RequiredUniqueChars = 0;
                //密码是否必须包含小写的ASCII字符，默认为true
                options.Password.RequireLowercase = false;
                //密码是否必须包含非字母数字字符，默认为true
                options.Password.RequireNonAlphanumeric = false;
                //密码是否必须包含大写的ASCII字符，默认为true
                options.Password.RequireUppercase = false;
                #endregion
                #region "用户验证选项"
                //用户名中用于验证用户名的允许字符列表。默认为"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz012456789-_@+"
                //options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz012456789-_@+";
                //是否需要为其用户提供唯一的电子邮件，默认为false
                options.User.RequireUniqueEmail = false;
                #endregion
                #region "用户锁定的选项"
                //是否可以锁定新用户的标志。默认为true。
                options.Lockout.AllowedForNewUsers = true;
                //在发生锁定时锁定用户的System.TimeSpan。默认为15分钟。
                options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
                //在锁定用户之前允许的失败访问尝试次数（假设已启用锁定）。默认为5。
                options.Lockout.MaxFailedAccessAttempts = 5;
                #endregion

            };
        }

        /// <summary>
        /// 重写以实现<see cref="CookieAuthenticationOptions"/>的配置
        /// </summary>
        /// <returns></returns>
        protected virtual Action<CookieAuthenticationOptions> CookieOptionsAction()
        {
            return options =>
            {
                options.Cookie.HttpOnly = true;
                options.Cookie.Name = "MicroCloud.identity";
                options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
                options.SlidingExpiration = true;
                options.LoginPath = "/#/identity/login";
            };
        }

        /// <summary>
        /// 重写以实现 AddIdentity 之后的构建逻辑
        /// </summary>
        /// <param name="builder"></param>
        /// <returns></returns>
        protected virtual IdentityBuilder OnIdentityBuild(IdentityBuilder builder)
        {
            return builder.AddDefaultTokenProviders();
        }

        #endregion
    }
}
